#include "pch.hpp"
#include "Context.hpp"

using namespace Engine;
using namespace Script;




Context::Context(pointer<asIScriptEngine> engine, Reflectable::weak_ptr reflectable)
	: m_context(engine->CreateContext())
	, m_reflectable(reflectable)
	, m_state(Idle)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::post_create(ptr _this)
{
	m_this = _this;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Context::State Context::state() const
{
	return m_state;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::attach(Debugger::ptr debugger)
{
	m_debugger = debugger;

	if(debugger)
	{
		verify(m_context->SetLineCallback(asMETHOD(Context, lineCallback), this, asCALL_THISCALL) >= 0);
	}
	else
	{
		m_context->ClearLineCallback();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Context::prepare(int funcId)
{
	verify(m_context->Prepare(funcId) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setObject(void* object)
{
	verify(m_context->SetObject(object) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setArgByte(asUINT arg, asBYTE value)
{
	verify(m_context->SetArgByte(arg, value) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setArgWord(asUINT arg, asWORD value)
{
	verify(m_context->SetArgWord(arg, value) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setArgDWord(asUINT arg, asDWORD value)
{
	verify(m_context->SetArgDWord(arg, value) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setArgQWord(asUINT arg, asQWORD value)
{
	verify(m_context->SetArgQWord(arg, value) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setArgFloat(asUINT arg, float value)
{
	verify(m_context->SetArgFloat(arg, value) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setArgDouble(asUINT arg, double value)
{
	verify(m_context->SetArgDouble(arg, value) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setArgAddress(asUINT arg, void* addr)
{
	verify(m_context->SetArgAddress(arg, addr) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::setArgObject(asUINT arg, void* obj)
{
	verify(m_context->SetArgObject(arg, obj) >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Script::Callstack Context::callstack() const
{
	std::vector<Function> functions;
	int numFunctions = m_context->GetCallstackSize();
	functions.reserve(numFunctions + 1);

	for(int i = 0; i < numFunctions; ++i)
	{
		int funcId = m_context->GetCallstackFunction(i);
		verify(funcId > 0);
		auto func = pointer<asIScriptFunction>(m_context->GetEngine()->GetFunctionDescriptorById(funcId));

		functions.push_back(Function(m_reflectable, func));
	}

	return functions;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

std::vector<Variable> Context::globals() const
{
	std::vector<Variable> variables;

	// TODO: Implement

	return variables;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

std::vector<Variable> Context::variables(std::size_t stackLevel) const
{
	std::vector<Variable> variables;
	int varCount = m_context->GetVarCount(stackLevel);
	variables.reserve(varCount + 1);

	// This pointer, in case this is a method
	int typeId = m_context->GetThisTypeId(stackLevel);
	void* var  = m_context->GetThisPointer(stackLevel);
	const char* name = nullptr;

	if(typeId > 0)
	{
		auto type = Reflectable::ptr(m_reflectable)->type(typeId);
		variables.push_back(Variable("this", var, type));
	}

	for(int i = 0; i < varCount; ++i)
	{
		typeId = m_context->GetVarTypeId(i, stackLevel);
		var    = m_context->GetAddressOfVar(i, stackLevel);
		name   = m_context->GetVarName(i, stackLevel);

		auto type = Reflectable::ptr(m_reflectable)->type(typeId);
		variables.push_back(Variable(name, var, type));
	}

	return variables;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Context::execute()
{
	BOOST_SCOPE_EXIT((&m_state))
	{
		m_state = Idle;
	}
	BOOST_SCOPE_EXIT_END;

	verify(m_context->Execute() >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




asBYTE Context::getReturnByte()
{
	return m_context->GetReturnByte();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

asWORD Context::getReturnWord()
{
	return m_context->GetReturnWord();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

asDWORD Context::getReturnDWord()
{
	return m_context->GetReturnDWord();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

asQWORD Context::getReturnQWord()
{
	return m_context->GetReturnQWord();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

float Context::getReturnFloat()
{
	return m_context->GetReturnFloat();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

double Context::getReturnDouble()
{
	return m_context->GetReturnDouble();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void* Context::getReturnAddress()
{
	return m_context->GetReturnAddress();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void* Context::getReturnObject()
{
	return m_context->GetReturnObject();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void* Context::getAddressOfReturnValue()
{
	return m_context->GetAddressOfReturnValue();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Context::lineCallback(asIScriptContext*)
{
	m_debugger->lineCallback(ptr(m_this));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
